/*-------------------------------------------------------------------------
 *
 * rel.h
 *	  POSTGRES 关系描述符（或称 relcache 条目）定义。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/utils/rel.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef REL_H
#define REL_H

#include "access/tupdesc.h"
#include "access/xlog.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
#include "nodes/bitmapset.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
#include "storage/block.h"
#include "storage/relfilenode.h"
#include "storage/smgr.h"
#include "utils/relcache.h"
#include "utils/reltrigger.h"


/*
 * LockRelId 和 LockInfo 实际上属于 lmgr.h，但在此声明它们更方便，因为我们可以在 Relation 中拥有一个 LockInfoData 字段。
 */

typedef struct LockRelId
{
	Oid			relId;			/* 关系标识符 */
	Oid			dbId;			/* 数据库标识符 */
} LockRelId;

typedef struct LockInfoData
{
	LockRelId	lockRelId;
} LockInfoData;

typedef LockInfoData *LockInfo;

/*
 * 这是关系缓存条目的内容。
 */

typedef struct RelationData
{
	RelFileNode rd_node;		/* 关系物理标识符 */
	SMgrRelation rd_smgr;		/* 缓存文件句柄，或 NULL */
	int			rd_refcnt;		/* 引用计数 */
	BackendId	rd_backend;		/* 拥有后端 id，如果是临时关系 */
	bool		rd_islocaltemp; /* rel 是该会话的临时关系 */
	bool		rd_isnailed;	/* rel 在缓存中被固定 */
	bool		rd_isvalid;		/* relcache 条目是有效的 */
	bool		rd_indexvalid;	/* rd_indexlist 有效吗？（也包括 rd_pkindex 和 rd_replidindex） */
	bool		rd_statvalid;	/* rd_statlist 有效吗？ */

	/*----------
	 * rd_createSubid 是该关系已存活到的最高子事务 ID，或在当前顶层事务之前创建的关系或其 rd_node 为零。
	 * （IndexStmt.oldNode 导致新关系与旧 rd_node 的情况。）rd_firstRelfilenodeSubid 是
	 * rd_node 变化存活到的最高子事务 ID，或在当前顶层事务开始时 rd_node 的值匹配时为零。
	 * （回滚 rd_firstRelfilenodeSubid 所表示的子事务会将 rd_node 恢复为
	 * 当前顶层事务开始时的值。回滚任何较低的子事务则不会。）它们的准确性对于
	 * RelationNeedsWAL() 至关重要。
	 *
	 * rd_newRelfilenodeSubid 是最近的 relfilenode 变化已存活到的最高子事务 ID，或在当前事务中未改变（或我们忘记了更改它）。
	 * 此字段在非零时准确，但在一个关系在单个事务中有多个新 relfilenodes 的情况下
	 * 可能为零，其中一个发生在随后中止的子事务中，例如：
	 *		BEGIN;
	 *		TRUNCATE t;
	 *		SAVEPOINT save;
	 *		TRUNCATE t;
	 *		ROLLBACK TO save;
	 *		-- rd_newRelfilenodeSubid 现在被遗忘
	 *
	 * 如果每个 rd_*Subid 字段都是零，它们在 relcache.c 外是只读的。
	 * 通过更新 pg_class.reltablespace 和/或 pg_class.relfilenode 触发 rd_node 变化的文件调用
	 * RelationAssumeNewRelfilenode() 来更新 rd_*Subid。
	 *
	 * rd_droppedSubid 是关系最初已存活到的最高子事务 ID。在 relcache.c 外部可见的条目中，这始终为零。
	 */
	SubTransactionId rd_createSubid;	/* rel 是在当前事务中创建的 */
	SubTransactionId rd_newRelfilenodeSubid;	/* 更改 rd_node 到当前值的最高子事务 */
	SubTransactionId rd_firstRelfilenodeSubid;	/* 更改 rd_node 到任何值的最高子事务 */
	SubTransactionId rd_droppedSubid;	/* 与其他 Subid 集一起删除 */

	Form_pg_class rd_rel;		/* 关系元组 */
	TupleDesc	rd_att;			/* 元组描述符 */
	Oid			rd_id;			/* 关系的对象 ID */
	LockInfoData rd_lockInfo;	/* 锁管理器的锁定关系信息 */
	RuleLock   *rd_rules;		/* 重写规则 */
	MemoryContext rd_rulescxt;	/* rd_rules 的私有内存上下文（如果有） */
	TriggerDesc *trigdesc;		/* 触发器信息，若关系没有则为 NULL */
	/* 这里使用 "struct" 避免需要包含 rowsecurity.h: */
	struct RowSecurityDesc *rd_rsdesc;	/* 行安全策略，或 NULL */

	/* 由 RelationGetFKeyList 管理的数据： */
	List	   *rd_fkeylist;	/* 外键缓存信息的列表（见下文） */
	bool		rd_fkeyvalid;	/* 如果列表已被计算则为真 */

	/* 由 RelationGetPartitionKey 管理的数据: */
	PartitionKey rd_partkey;	/* 分区键，或 NULL */
	MemoryContext rd_partkeycxt;	/* rd_partkey 的私有上下文，如果有的话 */

	/* 由 RelationGetPartitionDesc 管理的数据: */
	PartitionDesc rd_partdesc;	/* 分区描述符，或 NULL */
	MemoryContext rd_pdcxt;		/* rd_partdesc的私有上下文（如果有） */

	/* 同上，适用于省略了分离分区的partdescs */
	PartitionDesc rd_partdesc_nodetached;	/* 没有分离部分的partdesc */
	MemoryContext rd_pddcxt;	/* rd_partdesc_nodetached的情况（如果有） */

	/*
	 * 被排除在rd_partdesc_nodetached之外的分区的pg_inherits.xmin。
	 * 这通知未来使用该partdesc的用户：如果此值不在活动快照的进行中，则该
	 * partdesc可以使用，否则他们必须构建一个新的。 （这与find_inheritance_children_extended所做的匹配）。
	 */
	TransactionId rd_partdesc_nodetached_xmin;

	/* RelationGetPartitionQual管理的数据： */
	List	   *rd_partcheck;	/* 分区CHECK条件 */
	bool		rd_partcheckvalid;	/* 如果列表已被计算则为真 */
	MemoryContext rd_partcheckcxt;	/* rd_partcheck 的私有上下文（如果有） */

	/* 数据由 RelationGetIndexList 管理： */
	List	   *rd_indexlist;	/* 关系上索引的 OID 列表 */
	Oid			rd_pkindex;		/* 主键的 OID，如果有的话 */
	Oid			rd_replidindex; /* 副本标识索引的 OID，如果有的话 */

	/* 数据由 RelationGetStatExtList 管理： */
	List	   *rd_statlist;	/* 扩展统计的 OID 列表 */

	/* 数据由 RelationGetIndexAttrBitmap 管理： */
	Bitmapset  *rd_indexattr;	/* 标识用于索引的列 */
	Bitmapset  *rd_keyattr;		/* 可以被外键引用的列 */
	Bitmapset  *rd_pkattr;		/* 包含在主键中的列 */
	Bitmapset  *rd_idattr;		/* 包含在副本标识索引中 */

	PublicationDesc *rd_pubdesc;	/* 发布描述符，或 NULL */

	/*
	 * rd_options 在 rd_rel 被加载到 relcache 条目时设置。
	 * 请注意，您不能查看 rd_rel 来获取这些数据。 NULL 表示“使用
	 * 默认值”。
	 */
	bytea	   *rd_options;		/* 解析的 pg_class.reloptions */

	/*
	 * 此关系的处理程序的 Oid。如果是索引，这是一个返回
	 * IndexAmRoutine 的函数，对于表类关系是一个返回
	 * TableAmRoutine 的函数。它与 rd_indam、rd_tableam 分开存储，因为
	 * 其查找需要 syscache 访问，但在 relcache 启动期间，我们
	 * 需要能够在没有 syscache 查找的情况下初始化 rd_tableam。
	 */
	Oid			rd_amhandler;	/* 索引 AM 的处理程序函数的 OID */

	/*
	 * 表访问方法。
	 */
	const struct TableAmRoutine *rd_tableam;

	/* 这些仅对索引关系为非 NULL： */
	Form_pg_index rd_index;		/* 描述此索引的 pg_index 元组 */
	/* 在这里使用“struct”，以避免需要包含 htup.h： */
	struct HeapTupleData *rd_indextuple;	/* pg_index 元组的所有内容 */

	/*
	 * 索引访问支持信息（仅用于索引关系）
	 *
	 * 注意：仅缓存每个操作类的默认支持过程，即
	 * 左类型和右类型等于操作类的 opcintype 的那些。数组按支持
	 * 函数编号索引，这是给定限制的足够标识符。
	 */
	MemoryContext rd_indexcxt;	/* 用于这些内容的私有内存上下文 */
	/* 在这里使用“struct”，以避免需要包含 amapi.h： */
	struct IndexAmRoutine *rd_indam;	/* 索引 AM 的 API 结构 */
	Oid		   *rd_opfamily;	/* 每个索引列的操作族 OIDs */
	Oid		   *rd_opcintype;	/* opclass声明的输入数据类型的OID */
	RegProcedure *rd_support;	/* 支持过程的 OID */
	struct FmgrInfo *rd_supportinfo;	/* 支持过程的查找信息 */
	int16	   *rd_indoption;	/* 每列特定于 AM 的标志 */
	List	   *rd_indexprs;	/* 索引表达式树，如果有的话 */
	List	   *rd_indpred;		/* 索引谓词树，如果有的话 */
	Oid		   *rd_exclops;		/* 排除操作符的 OID，如果有的话 */
	Oid		   *rd_exclprocs;	/* 排除操作的过程 OID，如果有的话 */
	uint16	   *rd_exclstrats;	/* 排除操作的策略编号，如果有的话 */
	Oid		   *rd_indcollation;	/* 索引排序的 OID */
	bytea	  **rd_opcoptions;	/* 解析的操作类别特定选项 */

	/*
	 * rd_amcache 可用于索引和表 AM 缓存关于关系的私有数据
	 * 这必须仅仅是一个缓存，因为它可能会在任何时候被重置
	 * （尤其是，它将因关系的 relcache 无效消息而被重置）。如果使用，必须指向一个在 CacheMemoryContext
	 * 中分配的单一内存块，或对于索引关系则是在 rd_indexcxt 中。关系缓存重置将包括释放该块并设置
	 * rd_amcache = NULL。
	 */
	void	   *rd_amcache;		/* 可供索引/表 AM 使用 */

	/*
	 * 外部表支持
	 *
	 * rd_fdwroutine 必须指向一个在 CacheMemoryContext 中分配的单一内存块。
	 * 它将在关系缓存重置时被释放并重置为 NULL。
	 */

	/* 这里使用 "struct" 是为了避免需要包含 fdwapi.h： */
	struct FdwRoutine *rd_fdwroutine;	/* 缓存的函数指针，或 NULL */

	/*
	 * 用于 CLUSTER、重写 ALTER TABLE 等的 Hack：在写入表的新版本时，
	 * 我们需要确保插入的任何 toast 指针具有现有 toast 表的 OID，而不是 transient toast
	 * 表的 OID。如果 rd_toastoid 不是 InvalidOid，它就是要放入
	 * 此关系中插入的 toast 指针的 OID。（注意它是在主堆的新
	 * 版本上设置的，而不是在 toast 表本身上。）这还导致
	 * toast_save_datum() 尝试保留 toast 值的 OID。
	 */
	Oid			rd_toastoid;	/* 真实 TOAST 表的 OID，或 InvalidOid */

	bool		pgstat_enabled; /* 是否应统计关系统计信息 */
	/* 这里使用 "struct" 是为了避免需要包含 pgstat.h： */
	struct PgStat_TableStatus *pgstat_info; /* 统计信息收集区域 */
} RelationData;


/*
 * ForeignKeyCacheInfo
 *		关于外键约束的 relcache 可以缓存的信息
 *
 * 这基本上只是 pg_constraint 中相关列的一个影像。
 * 我们将其设为 Node 的一个子类，以便可以对该列表
 * 使用 copyObject()，但我们还确保它是一个"平坦"对象，
 * 没有子结构，因此 list_free_deep() 足以释放这样的列表。
 * 每个外键列的数组可以是固定大小的，因为我们在外键约束中最多允许
 * INDEX_MAX_KEYS 列。
 *
 * 目前，我们主要缓存与规划器相关的感兴趣字段，但字段集合
 * 已经扩展至其他用途的约束 OID。
 */
typedef struct ForeignKeyCacheInfo
{
	NodeTag		type;
	Oid			conoid;			/* 约束本身的 oid */
	Oid			conrelid;		/* 外键约束关系 */
	Oid			confrelid;		/* 外键引用的关系 */
	int			nkeys;			/* 外键中的列数 */
	/* 这些数组每个都有 nkeys 有效条目： */
	AttrNumber	conkey[INDEX_MAX_KEYS]; /* 引用表中的列 */
	AttrNumber	confkey[INDEX_MAX_KEYS];	/* 被引用表中的列 */
	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK 操作符 OID */
} ForeignKeyCacheInfo;


/*
 * StdRdOptions
 *		堆的 rd_options 的标准内容。
 *
 * RelationGetFillFactor() 和 RelationGetTargetPageFreeSpace() 只能
 * 应用于使用此格式或超集用于私有选项数据的关系。
 */
 /* 与 autovacuum 相关的 reloptions。 */
typedef struct AutoVacOpts
{
	bool		enabled;
	int			vacuum_threshold;
	int			vacuum_ins_threshold;
	int			analyze_threshold;
	int			vacuum_cost_limit;
	int			freeze_min_age;
	int			freeze_max_age;
	int			freeze_table_age;
	int			multixact_freeze_min_age;
	int			multixact_freeze_max_age;
	int			multixact_freeze_table_age;
	int			log_min_duration;
	float8		vacuum_cost_delay;
	float8		vacuum_scale_factor;
	float8		vacuum_ins_scale_factor;
	float8		analyze_scale_factor;
} AutoVacOpts;

/* StdRdOptions->vacuum_index_cleanup 值 */
typedef enum StdRdOptIndexCleanup
{
	STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
	STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
	STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON
} StdRdOptIndexCleanup;

typedef struct StdRdOptions
{
	int32		vl_len_;		/* varlena 头部（请勿直接触摸！） */
	int			fillfactor;		/* 页面填充因子百分比（0..100） */
	int			toast_tuple_target; /* 元组 toast 的目标 */
	AutoVacOpts autovacuum;		/* 与 autovacuum 相关的选项 */
	bool		user_catalog_table; /* 作为附加目录关系使用 */
	int			parallel_workers;	/* 最大并行工作者数 */
	StdRdOptIndexCleanup vacuum_index_cleanup;	/* 控制索引清理 */
	bool		vacuum_truncate;	/* 允许清理截断关系 */
} StdRdOptions;

#define HEAP_MIN_FILLFACTOR			10
#define HEAP_DEFAULT_FILLFACTOR		100

/*
 * RelationGetToastTupleTarget
 *		返回关系的 toast_tuple_target。注意参数的多重评估！
 */
#define RelationGetToastTupleTarget(relation, defaulttarg) \
	((relation)->rd_options ? \
	 ((StdRdOptions *) (relation)->rd_options)->toast_tuple_target : (defaulttarg))

/*
 * RelationGetFillFactor
 *		返回关系的填充因子。注意参数的多次求值！
 */
#define RelationGetFillFactor(relation, defaultff) \
	((relation)->rd_options ? \
	 ((StdRdOptions *) (relation)->rd_options)->fillfactor : (defaultff))

/*
 * RelationGetTargetPageUsage
 *		返回关系每页期望的空间使用量（以字节为单位）。
 */
#define RelationGetTargetPageUsage(relation, defaultff) \
	(BLCKSZ * RelationGetFillFactor(relation, defaultff) / 100)

/*
 * RelationGetTargetPageFreeSpace
 *		返回关系每页期望的空闲空间（以字节为单位）。
 */
#define RelationGetTargetPageFreeSpace(relation, defaultff) \
	(BLCKSZ * (100 - RelationGetFillFactor(relation, defaultff)) / 100)

/*
 * RelationIsUsedAsCatalogTable
 *		返回关系是否应被视为从逻辑解码的角度看属于目录表。注意参数的多次求值！
 */
#define RelationIsUsedAsCatalogTable(relation)	\
	((relation)->rd_options && \
	 ((relation)->rd_rel->relkind == RELKIND_RELATION || \
	  (relation)->rd_rel->relkind == RELKIND_MATVIEW) ? \
	 ((StdRdOptions *) (relation)->rd_options)->user_catalog_table : false)

/*
 * RelationGetParallelWorkers
 *		返回关系的 parallel_workers reloption 设置。
 *		注意参数的多次求值！
 */
#define RelationGetParallelWorkers(relation, defaultpw) \
	((relation)->rd_options ? \
	 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))

/* ViewOptions->check_option 值 */
typedef enum ViewOptCheckOption
{
	VIEW_OPTION_CHECK_OPTION_NOT_SET,
	VIEW_OPTION_CHECK_OPTION_LOCAL,
	VIEW_OPTION_CHECK_OPTION_CASCADED
} ViewOptCheckOption;

/*
 * ViewOptions
 *		视图的 rd_options 内容
 */
typedef struct ViewOptions
{
	int32		vl_len_;		/* varlena 头部（请勿直接触摸！） */
	bool		security_barrier;
	bool		security_invoker;
	ViewOptCheckOption check_option;
} ViewOptions;

/*
 * RelationIsSecurityView
 *		返回关系是否为安全视图。注意参数的多次求值！
 */
#define RelationIsSecurityView(relation)									\
	(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW),				\
	 (relation)->rd_options ?												\
	  ((ViewOptions *) (relation)->rd_options)->security_barrier : false)

/*
 * RelationHasSecurityInvoker
 *		如果关系设置了 security_invoker 属性，则返回 true。
 *		注意参数的多次求值！
 */
#define RelationHasSecurityInvoker(relation)								\
	(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW),				\
	 (relation)->rd_options ?												\
	  ((ViewOptions *) (relation)->rd_options)->security_invoker : false)

/*
 * RelationHasCheckOption
 *		如果关系是使用局部或级联检查选项定义的视图，则返回 true。
 *		注意参数的多次求值！
 */
#define RelationHasCheckOption(relation)									\
	(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW),				\
	 (relation)->rd_options &&												\
	 ((ViewOptions *) (relation)->rd_options)->check_option !=				\
	 VIEW_OPTION_CHECK_OPTION_NOT_SET)

/*
 * RelationHasLocalCheckOption
 *		如果关系是使用局部检查选项定义的视图，则返回 true。
 *		注意参数的多次求值！
 */
#define RelationHasLocalCheckOption(relation)								\
	(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW),				\
	 (relation)->rd_options &&												\
	 ((ViewOptions *) (relation)->rd_options)->check_option ==				\
	 VIEW_OPTION_CHECK_OPTION_LOCAL)

/*
 * RelationHasCascadedCheckOption
 *		如果关系是使用级联检查选项定义的视图，则返回 true。
 *		注意参数的多次求值！
 */
#define RelationHasCascadedCheckOption(relation)							\
	(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW),				\
	 (relation)->rd_options &&												\
	 ((ViewOptions *) (relation)->rd_options)->check_option ==				\
	  VIEW_OPTION_CHECK_OPTION_CASCADED)

/*
 * RelationIsValid
 *		只有当关系描述符有效时为真。
 */
#define RelationIsValid(relation) PointerIsValid(relation)

#define InvalidRelation ((Relation) NULL)

/*
 * RelationHasReferenceCountZero
 *		只有当关系引用计数为零时为真。
 *
 * 注意：
 *		假设关系描述符有效。
 */
#define RelationHasReferenceCountZero(relation) \
		((bool)((relation)->rd_refcnt == 0))

/*
 * RelationGetForm
 *		返回表示关系的 pg_class 元组。
 *
 * 注意：
 *		假设关系描述符有效。
 */
#define RelationGetForm(relation) ((relation)->rd_rel)

/*
 * RelationGetRelid
 *		返回关系的 OID。
 */
#define RelationGetRelid(relation) ((relation)->rd_id)

/*
 * RelationGetNumberOfAttributes
 *		返回关系中的属性总数。
 */
#define RelationGetNumberOfAttributes(relation) ((relation)->rd_rel->relnatts)

/*
 * IndexRelationGetNumberOfAttributes
 *		返回索引中的属性数量。
 */
#define IndexRelationGetNumberOfAttributes(relation) \
		((relation)->rd_index->indnatts)

/*
 * IndexRelationGetNumberOfKeyAttributes
 *		返回索引中的关键属性数量。
 */
#define IndexRelationGetNumberOfKeyAttributes(relation) \
		((relation)->rd_index->indnkeyatts)

/*
 * RelationGetDescr
 *		返回关系的元组描述符。
 */
#define RelationGetDescr(relation) ((relation)->rd_att)

/*
 * RelationGetRelationName
 *		返回关系的名称。
 *
 * 注意，该名称仅在包含的命名空间内是唯一的。
 */
#define RelationGetRelationName(relation) \
	(NameStr((relation)->rd_rel->relname))

/*
 * RelationGetNamespace
 *		返回关系的命名空间 OID。
 */
#define RelationGetNamespace(relation) \
	((relation)->rd_rel->relnamespace)

/*
 * RelationIsMapped
 *		如果关系使用了 relfilenode 映射，则为真。注意参数的多次求值！
 */
#define RelationIsMapped(relation) \
	(RELKIND_HAS_STORAGE((relation)->rd_rel->relkind) && \
	 ((relation)->rd_rel->relfilenode == InvalidOid))

#ifndef FRONTEND
/*
 * RelationGetSmgr
 *		返回关系的 smgr 文件句柄，如果需要则打开它。
 *
 * 只有极少数代码被授权直接接触 rel->rd_smgr。相反，
 * 请使用此函数获取其值。
 *
 * 注意：由于 relcache 刷新可能导致文件句柄被关闭，
 * 因此不建议长时间保持此函数返回的指针。
 * 推荐的做法是每次需要访问 SMgrRelation 时重新执行 RelationGetSmgr。
 * 相比 smgr 函数将要做的事情，这非常便宜。
 */
static inline SMgrRelation
RelationGetSmgr(Relation rel)
{
	if (unlikely(rel->rd_smgr == NULL))
		smgrsetowner(&(rel->rd_smgr), smgropen(rel->rd_node, rel->rd_backend));
	return rel->rd_smgr;
}
#endif							/* !FRONTEND */

/*
 * RelationCloseSmgr
 *		在 smgr 级别关闭关系（如果尚未关闭）。
 *
 * 注意：smgrclose 应该从所有者指针中解除挂钩，因此要断言。
 */
#define RelationCloseSmgr(relation) \
	do { \
		if ((relation)->rd_smgr != NULL) \
		{ \
			smgrclose((relation)->rd_smgr); \
			Assert((relation)->rd_smgr == NULL); \
		} \
	} while (0)

/*
 * RelationGetTargetBlock
 *		获取关系当前的插入目标块。
 *
 * 如果没有当前目标块，则返回 InvalidBlockNumber。注意
 * 目标块状态在任何 smgr 级别的失效时被丢弃，
 * 因此，如果当前没有打开 smgr 句柄，则无需重新打开它。
 */
#define RelationGetTargetBlock(relation) \
	( (relation)->rd_smgr != NULL ? (relation)->rd_smgr->smgr_targblock : InvalidBlockNumber )

/*
 * RelationSetTargetBlock
 *		设置关系当前的插入目标块。
 */
#define RelationSetTargetBlock(relation, targblock) \
	do { \
		RelationGetSmgr(relation)->smgr_targblock = (targblock); \
	} while (0)

/*
 * RelationIsPermanent
 *		如果关系是永久的，则为真。
 */
#define RelationIsPermanent(relation) \
	((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)

/*
 * RelationNeedsWAL
 *		如果关系需要 WAL，则为真。
 *
 * 如果 wal_level = minimal 并且此关系在当前事务中创建或
 * 截断，则返回 false。请参见 src/backend/access/transam/README 中的“跳过新
 * RelFileNode 的 WAL”。
 */
#define RelationNeedsWAL(relation)										\
	(RelationIsPermanent(relation) && (XLogIsNeeded() ||				\
	  (relation->rd_createSubid == InvalidSubTransactionId &&			\
	   relation->rd_firstRelfilenodeSubid == InvalidSubTransactionId)))

/*
 * RelationUsesLocalBuffers
 *		如果关系的页面存储在本地缓冲区，则为真。
 */
#define RelationUsesLocalBuffers(relation) \
	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)

/*
 * RELATION_IS_LOCAL
 *		如果一个关系在当前事务中是临时的或新创建的，
 *		则可以假设它仅对当前后端可访问。
 *		这通常用于决定我们可以跳过获取锁。
 *
 * 注意多次评估参数
 */
#define RELATION_IS_LOCAL(relation) \
	((relation)->rd_islocaltemp || \
	 (relation)->rd_createSubid != InvalidSubTransactionId)

/*
 * RELATION_IS_OTHER_TEMP
 *		测试属于其他会话的临时关系。
 *
 * 注意多次评估参数
 */
#define RELATION_IS_OTHER_TEMP(relation) \
	((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
	 !(relation)->rd_islocaltemp)


/*
 * RelationIsScannable
 *		目前只有在材料化视图未被查询填充时为假。
 *		这可能会在后面变得更加复杂，
 *		因此使用看起来像函数的宏。
 */
#define RelationIsScannable(relation) ((relation)->rd_rel->relispopulated)

/*
 * RelationIsPopulated
 *		目前，我们没有在物理上区分材料化视图的“已填充”和
 *		“可扫描”属性，但这可能会在以后发生变化。
 *		因此，在代码测试中使用这些宏中的适当一个。
 */
#define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated)

/*
 * RelationIsAccessibleInLogicalDecoding
 *		如果我们需要记录足够的信息以便通过
 *		解码快照访问，则为真。
 */
#define RelationIsAccessibleInLogicalDecoding(relation) \
	(XLogLogicalInfoActive() && \
	 RelationNeedsWAL(relation) && \
	 (IsCatalogRelation(relation) || RelationIsUsedAsCatalogTable(relation)))

/*
 * RelationIsLogicallyLogged
 *		如果我们需要记录足够的信息以从
 *		WAL 流中提取数据，则为真。
 *
 * 我们不记录未记录表的信息（因为它们根本不进行 WAL 记录），
 * 对外部表（因为它们也不进行 WAL 记录），
 * 以及对系统表（它们的内容很难理解，
 * 而且会稍微复杂解码，收益不大）。请注意，我们确实
 * 记录用户定义的目录表的信息，因为它们可能对用户来说是
 * 有趣的...
 */
#define RelationIsLogicallyLogged(relation) \
	(XLogLogicalInfoActive() && \
	 RelationNeedsWAL(relation) && \
	 (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&	\
	 !IsCatalogRelation(relation))

/* utils/cache/relcache.c 中的例程 */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);

#endif							/* REL_H */
